C++中基本数据类型的表现形式
本文为看雪论坛精华文章
看雪论坛作者ID:techliu
在32位计算机中,数据都是以DWORD(双字)的形式存储的。
对于不同的整数类型有不同的存储机制,例如无符号整数的可表示的数值大小要比有符号整数大一倍,有符号整数中负数和正数的表示是不一样的。
不管是有符号还是无符号,在计算机内存中存储的时候都是“小端序”形式存放的,即高字节放在高地址,低字节放在低地址,注意是以字节为单位,而不是以1个位。
1、无符号整数
无符号整数在C++中用unsigned int关键字表示,占4字节,32位上的每一位都表示数值,可表示数值范围为:0x00000000~0xFFFFFFFF,十进制表示为:0~4294967295。
当无符号整型不足32位时,用0来填充剩余高位,直到占满4字节内存空间为止。
因为无符号整数每一位都是用来表示数值的,所以无符号整数在内存中都是以真值的形式存放的。
2、有符号整数
有符号整数在C++中用int关键字表示,占4字节,有符号整数最高位用来表示符号,被称为符号位,最高位为0表示正数,反之表示负数。
故被用来表示数值的只有31位,其表示的数值范围为:0x80000000~0x7FFFFFFF,
对应二进制表示为:-2147483648~2147483647。
对于细心的同学可能发现了一个问题,最高位是符号位,0x80000000对应的二进制为 1000 0000 0000 0000 0000 0000 0000 0000,这么一看不应该是 -0 吗?
这里先说一下内存中负数是怎么存储的,负数的存储采用了补码的形式,不论是补码还是反码都是根据原码发展而来的。
原码是未经更改的加上左边符号位的二进制码,而补码是数字的绝对值的原码基础上取反加1得到的,现代计算机中用补码表示负数,其优点是可以在加法或减法处理中,不需因为数字的正负而使用不同的计算方式。
按照补码的形式存储负数,如果不考虑0x80000000,最小的负数应该是0x80000001,取反加1后可得到原码为0x7FFFFFFF,故0x80000001表示 -2147483647。
这时候再看 0x80000000 ,他既可以表示 -0,又可以表示 0x80000001 - 1,因为没必要存在两个0的表示形式,所以规定 0x80000000 表示的是 0x80000000 -1 ,即 -2147483648。
综上,正数范围为 0x00000000~0x7FFFFFFF,负数范围为 0x80000000~0xFFFFFFFF。
1、定点实数存储方式
> 小数点的位置固定。如果用4字节存储实数,2字节用来存储整数部分,2字节用来存储小数部分。
> 虽然运算效率高,但是不灵活,如果数据超出2字节就无法存储了。
> 小数点的位置不固定。利用几个二进制位表示小数点位置,称为“指数域”,剩下的表示“数据域”和“符号域”。在计算的时候,先取出指数域,然后分割数据域得到真值,例如:655.35,指数域存储10的-2次方,数据域存储65535,运算的时候拿出来算一下就得到了真值。
对于现代计算机,CPU的不断升级革新,浮点实数存储方式已经普及,只有一些嵌入式设备上还能看到定点存储。
在 C++ 中,有两种表示浮点数的方式,“float”用4字节表示浮点数,“double”用8字节表示浮点数。
浮点数的运算不会用到通用寄存器,而是通过浮点协处理器提供的浮点寄存器对浮点数进行运算处理。
VC++ 6.0 中在使用浮点数前,都要对浮点寄存器进行初始化,然后才能正常运行。未初始化的时候会报错,例如:
int mian(void)
{
// 未使用到浮点数情况下,
int nInt = 0;
// 在VC++ 6.0 中输入小数会报错,因没有初始化浮点寄存器
scanf("%f", &nInt);
return 0;
}
如果在代码中任意位置定义一个浮点类型的变量,浮点寄存器初始化,就不会报错了。
再贴一段代码,看看运行结果:
void main(void){
int num=9; /* num是整型变量,设为9 */
float* pFloat=(float*)# /* pFloat表示num的内存地址,但是设为浮点数 */
printf("num的值为:%d\n",num); /* 显示num的整型值 */
printf("*pFloat的值为:%f\n",*pFloat); /* 显示num的浮点值 */
*pFloat=9.0; /* 将num的值改为浮点数 */
printf("num的值为:%d\n",num); /* 显示num的整型值 */
printf("*pFloat的值为:%f\n",*pFloat); /* 显示num的浮点值 */
}
运行结果如下:
num的值为:9
*pFloat的值为:0.000000
num的值为:1091567616
*pFloat的值为:9.000000
完全不理解这个结果是怎么回事,为什么浮点数和整数结果差别这么大?要理解这个结果,一定要搞懂浮点数在计算机内部的表示方法。
浮点数的编码方式
1、float类型的IEEE编码
V = (-1)*S*(M<<E)
其中 V 是浮点数,S是符号数(0或1),M为尾数(有效数位),E为指数位。
符号位:0 指数位:1000 0010 尾数位:1000 1000 0000 0000 0000 000
转换成十六进制数为 0x41440000,内存中以小端序方式存储,故为 00 00 44 41。
(2)E全为0。这是非正常表示数,M的值为0~1之间的小数,真正指数E的取值为1-127=-126,多这么一个1是为了补偿尾数中去掉的1,尾数为0.xxxxxx的小数。这种情况是为了表示±0.0,以及接近于0.0的很小的数字。
(3)E全为1。这是特殊值,如果尾数M全为0,表示±无穷大(正负取决于符号位s);如果尾数M不全为0,表示这个数不是一个数(NaN)。
2、double类型的IEEE编码
对于double类型的转换,照猫画虎即可!
基本的浮点数指令
void main(int argc){
float fFloat = (float)argc;
printf("%f\n", fFloat);
}
00410941 mov ebp,esp
00410943 push ecx
00410944 fild dword ptr ss:[ebp+0x8] ; 把ebp+8处的整型转换为浮点型,压入浮点寄存器,对应变量argc
00410947 fst dword ptr ss:[ebp-0x4] ; 取出浮点数,以浮点数编码形式存放在ebp-4中,对应变量fFloat
0041094A sub esp,0x8 ; 栈顶分配一个double空间
0041094D fstp qword ptr ss:[esp] ; float传入可变参函数中要先转换为double,以浮点编码形式存入esp中
00410950 push ReverseT.00418E74 ; "%d\n"
00410955 call ReverseT.00401040 ; printf("%d\n", a)
0041095A add esp,0xC ; __cdelc约定,调用者平衡栈
0041095D mov esp,ebp
0041095F pop ebp
00410960 retn
float fFloat = (float)argc;
printf("%f\n", fFloat);
argc = (int)fFloat;
printf("%d\n", argc);
}
0041095D fld dword ptr ss:[ebp-0x4] ; 将ebp-4处的数据放入浮点寄存器,对应变量fFloat
00410960 call ReverseT.00410910 ; 调用__ftol
00410965 mov dword ptr ss:[ebp+0x8],eax ; 把转换后的结果,放入ebp+8中,对应变量argc
00410968 mov eax,dword ptr ss:[ebp+0x8] ; 下面是调用printf
0041096B push eax
0041096C push ReverseT.00418E78
00410971 call ReverseT.00401040
00410976 add esp,0x8
float GetFloat()
{
return 12.05f;
}
void main(int argc){
float fFloat = GetFloat();
printf("%f\n", fFloat);
}
0040101C mov ebp,esp
0040101E push ecx
0040101F call ReverseT.0040100A ; 调用GetFloat
;;;;;;;;;;;;;;;call 0040100A;;;;;;;;;;;;;;;;
00401010 push ebp
00401011 mov ebp,esp
00401013 fld dword ptr ds:[0x416344] ; 将浮点数放入浮点寄存器中
00401019 pop ebp
0040101A retn
;;;;;;;;;;;;;;;end 0040100A;;;;;;;;;;;;;;;;;
00401024 fst dword ptr ss:[ebp-0x4] ; 从浮点寄存器中取出数据放入ebp-4中,对应变量fFloat
00401027 sub esp,0x8 ; 调用printf
0040102A fstp qword ptr ss:[esp]
0040102D push ReverseT.00418A30 ; "%f\n"
00401032 call ReverseT.00401040
00401037 add esp,0xC
0040103A mov esp,ebp
0040103C pop ebp
0040103D retn
1、字符的编码
char* s = "汉子文化";
printf("%s\n", s);
Press any key to continue
printf("%c\n",s);
Press any key to continue
2、字符串的存储方式
int main(void)
{
char* pcChar = "string!";
wchar_t* pwChar = L"wide string!";
return 0;
}
int main()
{
setlocale(LC_ALL, ".936");
wchar_t c = L'汉';
wprintf(L"%lc\n", c);
return 0;
}
地址和指针的关系
指针的加和减
int main()
{
int array[] = {1,2,3,4,5};
int * piArray = array;
int i = 0;
for(i = 0; i < 5; i++)
{
printf("%d\n", *piArray);
piArray += 1;
}
return 0;
}
00401013 push esi
00401014 push edi
00401015 mov edi,0x5 ; 对应变量i,这里是自减循环
0040101A mov dword ptr ss:[esp+0x8],0x1 ; 在栈上创建数组
00401022 mov dword ptr ss:[esp+0xC],0x2
0040102A mov dword ptr ss:[esp+0x10],0x3
00401032 mov dword ptr ss:[esp+0x14],0x4
0040103A mov dword ptr ss:[esp+0x18],edi
0040103E lea esi,dword ptr ss:[esp+0x8] ; 让esi指向数组,对应变量piArray
00401042 mov eax,dword ptr ds:[esi] ; 调用printf输出
00401044 push eax
00401045 push ReverseT.00414A30
0040104A call ReverseT.00401080
0040104F add esp,0x8
00401052 add esi,0x4 ; 根据指针类型大小加,而不是加1
00401055 dec edi
00401056 jnz short ReverseT.00401042
00401058 pop edi
00401059 xor eax,eax
0040105B pop esi
0040105C add esp,0x14
0040105F retn
引用
int main()
{
int iVar;
scanf("%d", &iVar);
printf("%d", iVar);
return 0;
}
00401011 lea eax,dword ptr ss:[esp] ; iVar的指针
00401015 push eax
00401016 push ReverseT.00414A30
0040101B call ReverseT.0040F890 ; 调用scanf
00401020 mov ecx,dword ptr ss:[esp+0x8]
00401024 add esp,0x8
00401027 push ecx
00401028 push ReverseT.00414A30
0040102D call ReverseT.00401080 ; 调用printf
00401032 xor eax,eax
00401034 add esp,0xC
00401037 retn
- End -
看雪ID:techliu
https://bbs.pediy.com/user-860174.htm
本文由看雪论坛 techliu 原创
转载请注明来自看雪社区
往期热门回顾
﹀
﹀
﹀
公众号ID:ikanxue
官方微博:看雪安全
商务合作:wsc@kanxue.com
↙点击下方“阅读原文”,查看更多干货